﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace Quail.DbLogger
{
    public class QuailDbLoggerThread : IDisposable
    {
        private static readonly EventWaitHandle Terminate = new EventWaitHandle(false, EventResetMode.ManualReset);
        private static readonly WaitableConcurrentQueue<IQuailDbLoggerEntry> Entries = new WaitableConcurrentQueue<IQuailDbLoggerEntry>();
        private static readonly List<Thread> LoggingThreads = new List<Thread>();
        private static readonly Stopwatch CheckThreadsTimer = new Stopwatch();
        private static int _usageCounter;

        /// <summary>
        /// Initializes a new instance of the <see cref="QuailDbLoggerThread"/> class.
        /// </summary>
        public QuailDbLoggerThread()
        {
            var counter = Interlocked.Increment(ref _usageCounter);
            if (counter ==1)
            {
                lock(LoggingThreads)
                {
                    Terminate.Reset();

                    if (CheckThreadsTimer.IsRunning)
                        CheckThreadsTimer.Restart();
                    else
                        CheckThreadsTimer.Start();

                    for (int threadCount = 0; threadCount < 3; threadCount++)
                        LoggingThreads.Add(new Thread(LoggingThreadMethod));

                    foreach(var thread in LoggingThreads)
                        thread.Start();
                }
            }
        }

        ~QuailDbLoggerThread()
        {
            Dispose(false);
        }

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                var counter = Interlocked.Decrement(ref _usageCounter);

                if (counter == 0)
                {
                    Terminate.Set();

                    lock (LoggingThreads)
                    {
                        foreach (var thread in LoggingThreads)
                            thread.Join();

                        LoggingThreads.Clear();
                    }
                }                
            }
        }

        #endregion

        /// <summary>
        /// Adds a new logger entry to the queue
        /// </summary>
        /// <param name="entry">The entry.</param>
        public static void AddEntry(IQuailDbLoggerEntry entry)
        {
            Entries.Enqueue(entry);

            // see if we need more logging worker threads
            if (Entries.Count > 500 && 
                CheckThreadsTimer.ElapsedMilliseconds > 45000 &&
                Monitor.TryEnter(LoggingThreads, 0))
            {
                try
                {
                    if (CheckThreadsTimer.ElapsedMilliseconds > 45000)
                    {
                        var started = false;
                        foreach (var t in LoggingThreads)
                        {
                            if (t.Join(0))
                            {
                                // restart the thread
                                t.Start();
                                started = true;
                            }
                        }

                        if (!started && LoggingThreads.Count < 10)
                        {
                            var t = new Thread(LoggingThreadMethod);
                            t.Start();
                            LoggingThreads.Add(t);
                        }

                        CheckThreadsTimer.Restart();
                    }
                }
                finally
                {
                    Monitor.Exit(LoggingThreads);
                }
            }
        }

        private static void LoggingThreadMethod()
        {
            var waits = new [] { Terminate, Entries.WaitOn};
            
            try
            {
                using (var client = QuailServices.QuailDataServiceFactory.CreateClient())
                {
                    for (;;)
                    {
                        var waitResult = WaitHandle.WaitAny(waits);

                        if (waitResult == 0)
                        {
                            while (Entries.WaitOn.WaitOne(0))
                                ProcessEntry(client, Entries.Dequeue());

                            break;
                        }

                        ProcessEntry(client, Entries.Dequeue());
                    }
                }
            }
            catch(Exception e)
            {
                string msg = e.Message;
                Console.WriteLine("QuailDbLoggerThread exception: {0}", msg);
            }
        }

        private static void LogSomething(string text)
        {
            try
            {
                lock (LogFile)
                {
                    using (var file = System.IO.File.AppendText(@"e:\quaillog.txt"))
                    {
                        file.WriteLine("{0} {1}", DateTime.Now, text);
                    }
                }
            }
            catch (Exception e2)
            {
                // ignore
            }            
        }

        private static readonly object LogFile = new object();

        private static void ProcessEntry(QuailServices.QuailDataServiceReference.QuailDataServiceClient client, IQuailDbLoggerEntry entry)
        {
            try
            {
                entry.Store(client);
            }
            catch (Exception e)
            {
                entry.FailCounter++;

                LogSomething(string.Format("{2} {0} {1}", entry.GetLogDetail(), e.Message, entry.FailCounter));

                if (entry.FailCounter < 10)
                    AddEntry(entry);
            }
        }
    }
}
